<template>
  <view class="rtc-page">
    <view class="rtc-header">
      <view class="left" @click="goBack">← 返回</view>
      <view class="title">视频通话</view>
      <view class="right"></view>
    </view>

    <view class="rtc-body">
      <view class="videos">
        <video
          id="remoteVideo"
          class="remote-video"
          autoplay
          playsinline
          :srcObject="remoteStream"
          v-if="remoteReady"
        ></video>
        <view class="remote-placeholder" v-else>等待对方加入...</view>

        <video
          id="localVideo"
          class="local-video"
          autoplay
          muted
          playsinline
          :srcObject="localStream"
          v-if="localReady"
        ></video>
      </view>

      <view class="status">
        <text>房间ID：{{ roomId }}</text>
        <text v-if="!wsConnected" class="warn">未连接信令服务，仅本地预览，无法建立通话</text>
      </view>

      <view class="controls">
        <button class="ctrl" @click="toggleMic">{{ micEnabled ? '麦克风开' : '麦克风关' }}</button>
        <button class="ctrl" @click="toggleCam">{{ camEnabled ? '摄像头开' : '摄像头关' }}</button>
        <button class="ctrl primary" @click="startOrJoin">{{ inCall ? '已在通话' : (isCaller ? '发起通话' : '加入通话') }}</button>
        <button class="ctrl danger" @click="hangup" :disabled="!inCall">挂断</button>
      </view>
    </view>
  </view>
</template>

<script>
import config from '@/config.js'
import ws from '@/utils/ws.js'

export default {
  data() {
    return {
      roomId: '',
      isCaller: true,
      pc: null,
      localStream: null,
      remoteStream: null,
      localReady: false,
      remoteReady: false,
      micEnabled: true,
      camEnabled: true,
      inCall: false,
      wsConnected: false,
      iceQueue: [],
    }
  },
  onLoad(query) {
    this.roomId = (query.roomId || `room_${Date.now()}`)
    this.isCaller = (query.role || 'caller') === 'caller'

    // 监听全局 WS
    this._onWsOpen = () => { this.wsConnected = true }
    this._onWsClose = () => { this.wsConnected = false }
    this._onWsMessage = (evt) => this.onSignalMessage(evt)
    uni.$on('global-ws-open', this._onWsOpen)
    uni.$on('global-ws-close', this._onWsClose)
    uni.$on('global-ws-message', this._onWsMessage)

    this.wsConnected = ws.isConnected()

    // 连接信令服务（基于 roomId 分房间），地址从 config.signalBaseUrl 读取
    try {
      const base = (config && config.signalBaseUrl) ? config.signalBaseUrl.replace(/\/$/, '') : 'ws://localhost:8081'
      const url = `${base}?roomId=${encodeURIComponent(this.roomId)}`
      ws.connect(url)
    } catch (e) {}

    // 预采集本地媒体，用户体验更好
    this.prepareLocalMedia()
  },
  onUnload() {
    try {
      uni.$off('global-ws-open', this._onWsOpen)
      uni.$off('global-ws-close', this._onWsClose)
      uni.$off('global-ws-message', this._onWsMessage)
    } catch (e) {}
    this.hangup()
  },
  methods: {
    goBack() { uni.navigateBack() },

    async prepareLocalMedia() {
      // #ifndef H5
      uni.showToast({ title: 'WebRTC 仅支持 H5 端', icon: 'none' })
      // #endif
      try {
        const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true })
        this.localStream = stream
        this.localReady = true
        // 将流绑定到 video（某些端需要手动）
        this.$nextTick(() => {
          const v = document.getElementById('localVideo')
          if (v && 'srcObject' in v) v.srcObject = stream
        })
      } catch (e) {
        console.error('采集本地媒体失败', e)
        uni.showToast({ title: '无法访问摄像头/麦克风', icon: 'none' })
      }
    },

    async startOrJoin() {
      if (this.inCall) return
      await this.ensurePC()
      if (this.isCaller) {
        await this.makeOffer()
      } else {
        uni.showToast({ title: '等待来电/Offer...', icon: 'none' })
      }
    },

    async ensurePC() {
      if (this.pc) return this.pc
      const pc = new RTCPeerConnection({
        iceServers: [
          { urls: 'stun:stun.l.google.com:19302' },
        ]
      })
      this.pc = pc

      // 绑定本地 track
      if (this.localStream) {
        this.localStream.getTracks().forEach(t => pc.addTrack(t, this.localStream))
      }

      // 远端流
      const remote = new MediaStream()
      this.remoteStream = remote
      pc.ontrack = (evt) => {
        evt.streams[0].getTracks().forEach(t => remote.addTrack(t))
        this.remoteReady = true
        this.$nextTick(() => {
          const rv = document.getElementById('remoteVideo')
          if (rv && 'srcObject' in rv) rv.srcObject = remote
        })
      }

      // ICE
      pc.onicecandidate = (evt) => {
        if (evt.candidate) {
          this.sendSignal({ type: 'candidate', roomId: this.roomId, candidate: evt.candidate })
        }
      }

      pc.onconnectionstatechange = () => {
        const s = pc.connectionState
        if (s === 'connected') this.inCall = true
        if (s === 'disconnected' || s === 'failed' || s === 'closed') this.inCall = false
      }

      return pc
    },

    async makeOffer() {
      if (!this.wsConnected) {
        return uni.showToast({ title: '信令未连接，无法发起通话', icon: 'none' })
      }
      const pc = await this.ensurePC()
      const offer = await pc.createOffer()
      await pc.setLocalDescription(offer)
      this.sendSignal({ type: 'offer', roomId: this.roomId, sdp: offer })
    },

    async onSignalMessage(evt) {
      try {
        const msg = typeof evt.data === 'string' ? JSON.parse(evt.data) : evt.data
        if (!msg || msg.roomId !== this.roomId) return
        if (!this.pc && (msg.type === 'offer' || msg.type === 'answer' || msg.type === 'candidate')) {
          await this.ensurePC()
        }
        if (msg.type === 'offer') {
          await this.pc.setRemoteDescription(new RTCSessionDescription(msg.sdp))
          const answer = await this.pc.createAnswer()
          await this.pc.setLocalDescription(answer)
          this.sendSignal({ type: 'answer', roomId: this.roomId, sdp: answer })
        } else if (msg.type === 'answer') {
          await this.pc.setRemoteDescription(new RTCSessionDescription(msg.sdp))
        } else if (msg.type === 'candidate') {
          if (msg.candidate) {
            try { await this.pc.addIceCandidate(new RTCIceCandidate(msg.candidate)) } catch (e) { console.warn('addIceCandidate fail', e) }
          }
        }
      } catch (e) {
        // 忽略非本协议消息
      }
    },

    sendSignal(payload) {
      try {
        ws.send(payload)
      } catch (e) {}
    },

    toggleMic() {
      if (!this.localStream) return
      this.micEnabled = !this.micEnabled
      this.localStream.getAudioTracks().forEach(t => t.enabled = this.micEnabled)
    },
    toggleCam() {
      if (!this.localStream) return
      this.camEnabled = !this.camEnabled
      this.localStream.getVideoTracks().forEach(t => t.enabled = this.camEnabled)
    },

    hangup() {
      try {
        this.inCall = false
        if (this.pc) {
          this.pc.ontrack = null
          this.pc.onicecandidate = null
          this.pc.onconnectionstatechange = null
          this.pc.getSenders().forEach(s => { try { s.track && s.track.stop() } catch (e) {} })
          this.pc.close()
          this.pc = null
        }
        if (this.localStream) {
          this.localStream.getTracks().forEach(t => t.stop())
          this.localStream = null
          this.localReady = false
        }
        if (this.remoteStream) {
          this.remoteStream.getTracks().forEach(t => t.stop())
          this.remoteStream = null
          this.remoteReady = false
        }
      } catch (e) {}
    }
  }
}
</script>

<style scoped>
.rtc-page { height: 100vh; display: flex; flex-direction: column; background: #111; color: #fff; }
.rtc-header { height: 88rpx; display: flex; align-items: center; justify-content: space-between; padding: 0 24rpx; background: #1e1e1e; }
.rtc-header .left, .rtc-header .right { width: 160rpx; color: #ddd; }
.rtc-header .title { font-weight: 600; }
.rtc-body { flex: 1; display: flex; flex-direction: column; padding: 16rpx; gap: 16rpx; }
.videos { position: relative; flex: 1; background: #000; border-radius: 8rpx; overflow: hidden; }
.remote-video { width: 100%; height: 100%; object-fit: cover; }
.remote-placeholder { width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; color: #999; }
.local-video { position: absolute; right: 12rpx; bottom: 12rpx; width: 240rpx; height: 360rpx; background: #222; border-radius: 8rpx; border: 1px solid rgba(255,255,255,.2); object-fit: cover; }
.status { display: flex; gap: 20rpx; font-size: 24rpx; color: #bbb; }
.status .warn { color: #ffb74d; }
.controls { display: flex; gap: 16rpx; justify-content: center; }
.ctrl { background: #2d2d2d; color: #fff; padding: 12rpx 20rpx; border-radius: 8rpx; }
.ctrl.primary { background: #2e7d32; }
.ctrl.danger { background: #b71c1c; }
</style>
